package ipmanager;

import ipmanager.common.IP;
import ipmanager.common.IPAccess;
import ipmanager.common.IPClass;
import ipmanager.common.IPFormat;

import java.util.ArrayList;
import java.util.List;

public class ClassfullIP {

	protected IP ip;

	protected IP mask;

	protected IP redAddressIP;

	protected IP broadcastIP;

	protected int availableIPs;

	protected IP availableMinIP;

	protected IP availableMaxIP;

	private List<IP> availablesIP;

	protected String accessIP; // publica o privada

	protected String classIP;

	protected boolean validMask;

	/**
	 * Constructor de ClassfullIP
	 * 
	 * @param addressIP
	 * @param ipFormat
	 * @throws Exception
	 */
	public ClassfullIP(String addressIP, boolean showAvailablesIP)
			throws Exception {

		this.ip = new IP(addressIP, calculateIPFormat(addressIP));

		this.classIP = calculateClassIP(ip.getBinIP());

		this.mask = calculateMask(this.classIP);

		if (!classIP.equalsIgnoreCase(IPClass.CLASS_D)
				&& !classIP.equalsIgnoreCase(IPClass.CLASS_E)) {
			this.redAddressIP = calculateRedAddress(this.ip, this.mask);
			this.broadcastIP = calculateBroadcast(this.ip, this.mask);
			this.availableIPs = calculateAvailableIPs(this.mask);
			this.availableMinIP = calculateAvailableMinIP(this.redAddressIP);
			this.availableMaxIP = calculateAvailableMaxIP(this.broadcastIP);
			this.accessIP = calculateAccessIP(this.ip, this.classIP);
			if (showAvailablesIP)
				this.availablesIP = calculateAvailableIP(this.redAddressIP,
						this.broadcastIP);
		}

	}

	/**
	 * Calcula el formato en que se ingreso la direccion IP (bin, dec o hexa)
	 * 
	 * @param addressIP
	 * @return IPFormat.DECIMAL o IPFormat.BINARY o IPFormat.HEXA
	 */
	private String calculateIPFormat(String addressIP) {

		String ipFormat = IPFormat.DECIMAL;

		// Si la direccion IP tiene mas de 15 bits, entonces es binario
		if (addressIP.length() > 15)
			ipFormat = IPFormat.BINARY;
		else {
			String decimalAvailableChars = ".0123456789";
			for (int i = 0; i < addressIP.length(); i++) {
				if (decimalAvailableChars.indexOf(addressIP.charAt(i)) == -1) {
					ipFormat = IPFormat.HEXA;
					break;
				}
			}
		}

		return ipFormat;

	}

	/**
	 * Retorna la direccion IP ingresada
	 * 
	 * @return ip
	 */
	public IP getIP() {
		return ip;
	}

	/**
	 * Retorna la clase (A, B, C) de la direccion IP
	 * 
	 * @return classIP
	 */
	public String getClassIP() {
		return this.classIP;
	}

	/**
	 * Retorna la mascara
	 * 
	 * @return classIP
	 */
	public IP getMask() {
		return this.mask;
	}

	/**
	 * Retorna la direccion de red
	 * 
	 * @return redAddressIP
	 */
	public IP getRedAddressIP() {
		return this.redAddressIP;
	}

	/**
	 * Retorna la direccion broadcast
	 * 
	 * @return broadcastIP
	 */
	public IP getBroadcastIP() {
		return broadcastIP;
	}

	/**
	 * Retorna la cantidad de direcciones IP disponibles
	 * 
	 * @return availableIPs
	 */
	public int getAvailableIPs() {
		return availableIPs;
	}

	/**
	 * Retorna la minima direccion IP disponible
	 * 
	 * @return availableMinIP
	 */
	public IP getAvailableMinIP() {
		return availableMinIP;
	}

	/**
	 * Retorna la maxima direccion IP disponible
	 * 
	 * @return availableMaxIP
	 */
	public IP getAvailableMaxIP() {
		return availableMaxIP;
	}

	/**
	 * Retorna el acceso de la direccion IP (publica o privada)
	 * 
	 * @return accessIP
	 */
	public String getAccessIP() {
		return accessIP;
	}

	/**
	 * Retorna direccion IP binario
	 * 
	 * @return binIP
	 */
	public String getBinIP() {
		return ip.getBinIP();
	}

	/**
	 * Retorna direccion IP decimal
	 * 
	 * @return decIP
	 */
	public String getDecIP() {
		return ip.getDecIP();
	}

	/**
	 * Retorna direccion IP hexa
	 * 
	 * @return hexaIP
	 */
	public String getHexaIP() {
		return ip.getHexaIP();
	}

	/**
	 * Retorna las direcciones IP disponibles
	 * 
	 * @return availablesIP
	 */
	public List<IP> getAvailablesIP() {
		return availablesIP;
	}

	/**
	 * Retorna la clase (A, B, C) de una direccion IP
	 * 
	 * @param binIP
	 * @return clase A, B o C
	 */
	protected String calculateClassIP(String binIP) {

		if (binIP.startsWith("0"))
			return IPClass.CLASS_A;
		else if (binIP.startsWith("10"))
			return IPClass.CLASS_B;
		else if (binIP.startsWith("110"))
			return IPClass.CLASS_C;
		else if (binIP.startsWith("1110"))
			return IPClass.CLASS_D;
		else if (binIP.startsWith("1111"))
			return IPClass.CLASS_E;
		return null;
	}

	/**
	 * Calcula la mascara de una direccion IP a partir de la clase
	 * 
	 * @param classIP
	 * @return maskIP
	 * @throws Exception
	 */
	protected IP calculateMask(String classIP) throws Exception {
		if (classIP.equalsIgnoreCase(IPClass.CLASS_A))
			return new IP("255.0.0.0", IPFormat.DECIMAL);
		if (classIP.equalsIgnoreCase(IPClass.CLASS_B))
			return new IP("255.255.0.0", IPFormat.DECIMAL);
		if (classIP.equalsIgnoreCase(IPClass.CLASS_C))
			return new IP("255.255.255.0", IPFormat.DECIMAL);
		return null;
	}

	/**
	 * Calcula la direccion de red a partir de una direccion IP
	 * 
	 * @param ip
	 * @param mask
	 * @return redAddressIP
	 * @throws Exception
	 */
	private IP calculateRedAddress(IP ip, IP mask) throws Exception {

		IP redAddressIP = ip.andOperation(mask);

		return redAddressIP;
	}

	/**
	 * Calcula la direccion broadcast a partir de una direccion IP broadcast =
	 * direcIP OR mask
	 * 
	 * @param ip
	 * @param mask
	 * @return broadcastIP
	 * @throws Exception
	 */
	private IP calculateBroadcast(IP ip, IP mask) throws Exception {

		// Obtengo la negacion de la mascara
		IP noneMask = mask.noneOperation();

		IP broadcastIP = ip.orOperation(noneMask);

		return broadcastIP;
	}

	/**
	 * Calcula la cantidad de direcciones IP disponibles
	 * 
	 * @param mask
	 * @return availableIPs
	 */
	private int calculateAvailableIPs(IP mask) {

		int zeroCount = mask.getZeroCount();

		// Calculo 2(cant ceros) - 2
		int availableIPs = ((int) Math.pow(2, zeroCount)) - 2;

		return availableIPs;
	}

	/**
	 * Calcula la minima direccion IP que se puede asignar
	 * 
	 * @param redAddressIP
	 * @return availableMinIP
	 * @throws Exception
	 */
	private IP calculateAvailableMinIP(IP redAddressIP) throws Exception {

		IP ip = new IP("00000000.00000000.00000000.00000001", IPFormat.BINARY);

		IP addedIP = redAddressIP.addOperation(ip);

		return addedIP;
	}

	/**
	 * Calcula la maxima direccion IP que se puede asignar
	 * 
	 * @param broadcastIP
	 * @return availableMaxIP
	 * @throws Exception
	 */
	private IP calculateAvailableMaxIP(IP broadcastIP) throws Exception {

		IP ip = new IP("00000000.00000000.00000000.00000001", IPFormat.BINARY);

		IP restaIP = broadcastIP.restaOperation(ip);

		return restaIP;
	}

	/**
	 * Calcula si una direccion IP es publica o privada
	 * 
	 * @param ip
	 * @param classIP
	 * @return accessIP
	 * @throws Exception
	 */
	private String calculateAccessIP(IP ip, String classIP) throws Exception {

		String accessIP = "";
		String minPrivateIP = "";
		String maxPrivateIP = "";

		// Convierto las direcciones IP a binario puro (sin ".")
		if (classIP.equals(IPClass.CLASS_A)) {
			minPrivateIP = (new IP("10.0.0.0", IPFormat.DECIMAL)).getBinIP();
			maxPrivateIP = (new IP("10.255.255.255", IPFormat.DECIMAL))
					.getBinIP();
		} else if (classIP.equals(IPClass.CLASS_B)) {
			minPrivateIP = (new IP("172.26.0.0", IPFormat.DECIMAL)).getBinIP();
			maxPrivateIP = (new IP("172.31.255.255", IPFormat.DECIMAL))
					.getBinIP();
		} else if (classIP.equals(IPClass.CLASS_C)) {
			minPrivateIP = (new IP("192.168.0.0", IPFormat.DECIMAL)).getBinIP();
			maxPrivateIP = (new IP("192.168.255.255", IPFormat.DECIMAL))
					.getBinIP();
		}

		// Si la direccion es >= minPrivateIP y <= maxPrivateIP, entonces es
		// Privada
		if ((ip.getBinIP().compareTo(minPrivateIP) >= 0)
				&& (ip.getBinIP().compareTo(maxPrivateIP) <= 0)) {
			accessIP = IPAccess.PRIVATE_IP;
		} else
			accessIP = IPAccess.PUBLIC_IP;

		return accessIP;
	}

	/**
	 * Retorna las direcciones IP disponibles
	 * 
	 * @param redAddressesIP
	 * @param broadcastsIP
	 * @return availablesIP
	 * @throws Exception
	 */
	private List<IP> calculateAvailableIP(IP redAddressIP, IP broadcastIP)
			throws Exception {

		List<IP> availablesIP = new ArrayList<IP>();

		// Obtengo la direccion de red y broadcast i
		IP oneIP = new IP("00000000.00000000.00000000.00000001",
				IPFormat.BINARY);
		IP availableIP = new IP(redAddressIP.getBinIP(), IPFormat.BINARY);
		availableIP = availableIP.addOperation(oneIP);

		// Mientras la direccion IP available no sea broadcast, sumar 1
		while (!availableIP.getBinIP().equals((broadcastIP.getBinIP()))) {
			availablesIP.add(availableIP);
			availableIP = availableIP.addOperation(oneIP);
		}

		return availablesIP;
	}

}
